package com.lagou;

import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.common.constants.CommonConstants;
import org.apache.dubbo.common.extension.Activate;
import org.apache.dubbo.rpc.*;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;

import javax.annotation.PostConstruct;
import java.util.Arrays;
import java.util.Iterator;
import java.util.TreeMap;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * @author: 戎昊天
 * @date: 2020-08-28 22:30
 * @Description: ABC三个方法的计数器
 */
@Slf4j
@Component
@Activate(group = {CommonConstants.PROVIDER})
public class TPMonitorFilter implements Filter {
    /**
     * 方法延迟时间范围
     */
    public static final int DELAY_RANGE = 100;

    /**
     * 方法A B C 名称数组
     */
    private static final String[] methodNames = new String[]{"A", "B", "C"};

    /**
     * 方法A B C 的计时器数组
     */
    private static TreeMap<Long, AtomicInteger>[] methodsCounter = null;

    /**
     * 根据方法耗时计数
     *
     * @param time
     */
    private void count(int methodIdx, long time) {
        if (methodsCounter == null) {
            log.warn("计数器未初始化完成，抛弃计数");
        }
        Assert.isTrue(methodIdx < methodsCounter.length, "方法序号不正确");
        // 取方法对应的计数器
        TreeMap<Long, AtomicInteger> counter = methodsCounter[methodIdx];
        // 按方法计数器做同步控制
        synchronized (counter) {
            // 更新计数器次数
            AtomicInteger t = counter.get(time);
            if (t == null) {
                t = new AtomicInteger(0);
                methodsCounter[methodIdx].put(time, t);
            }
            t.addAndGet(1);
        }
    }

    @PostConstruct
    public void init() {
        resetCounter();
    }

    /**
     * 重置计数器
     *
     * @return 旧计数器
     */
    private TreeMap<Long, AtomicInteger>[] resetCounter() {
        //创建临时计时器，初始化归零后，完成后替换全局计数器
        @SuppressWarnings("unchecked")
        TreeMap<Long, AtomicInteger>[] tmpMethodsCounter = new TreeMap[3];
        for (int i = 0; i < 3; i++) {
            tmpMethodsCounter[i] = new TreeMap<>();
        }

        TreeMap<Long, AtomicInteger>[] oldCounterArr = methodsCounter;
        methodsCounter = tmpMethodsCounter;
        return oldCounterArr;
    }

    @Scheduled(fixedRate = 5000)
    public void print() {
        log.info(" ");
        log.info("--------------------------------------------");
        // 重置计数器，并获取重置前的旧计数器
        TreeMap<Long, AtomicInteger>[] oldCounterArr = resetCounter();
        // 迭代三个方法计数器
        for (int i = 0; i < oldCounterArr.length; i++) {
            // 取方法计数器
            TreeMap<Long, AtomicInteger> counter = oldCounterArr[i];
            log.info("Method{}: {}", methodNames[i], Arrays.toString(new TreeMap[]{counter}));
            // 获取计数器时间数量
            int quantity = counter.size();

            // 计算TP90
            Iterator<Long> it = counter.navigableKeySet().iterator();
            int tp90Pos = (int) Math.ceil(quantity * 0.9);
            for (int x = 0; x < tp90Pos - 1 && it.hasNext(); x++) {
                it.next();
            }
            long tp90 = it.hasNext()?it.next():0;

            // 计算TP99
            it = counter.navigableKeySet().iterator();
            int tp99Pos = (int) Math.ceil(quantity * 0.99);
            for (int x = 0; x < tp99Pos - 1 && it.hasNext(); x++) {
                it.next();
            }
            long tp99 = it.hasNext()?it.next():0;

            // 打印TP结果
            log.info("Method{}: TP90={}MS, TP99={}MS", methodNames[i], tp90, tp99);
        }
        log.info("--------------------------------------------");
        log.info(" ");
    }

    @Override
    public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
        // 计算方法调用时长
        long t1 = System.currentTimeMillis();
        Result result = invoker.invoke(invocation);
        long time = System.currentTimeMillis() - t1;
        count((Integer) result.getValue(), time);
        return result;
    }
}
